#include "SceneManager.h"
#include "MeshCache.h"
#include "XMeshLoader.h"
#include "FileSystem.h"
#include "D3DDriver.h"
#include "include/isnwVertex.h"
#include "CameraSceneNode.h"
#include "MeshSceneNode.h"
#include "AnimateMeshSceneNode.h"
#include "include/ITimer.h"
#include "BillboardSceneNode.h"
#include "ParticleSystemSceneNode.h"
#include "TerrainSceneNode.h"
#include "include/Random.h"
#include "SkyBoxSceneNode.h"

namespace ISNW
{
namespace scene
{
	SceneManager* g_pSceneManager = 0;

	void SceneManager::InitSceneManager()
	{
		static SceneManager sInstance;
		g_pSceneManager = &sInstance;
	}

	SceneManager::SceneManager()
		:ISceneNode(this,0,0,true)
		,meshcache(0)
		,ActiveCamera(0)
		,curr_id(0)
		,cull_type(E_CT_BOX)
	{
		//! new mesh cache
		meshcache = new MeshCache;

		//! new mesh loaders
		meshloaders.push_back(new XMeshLoader);

		//! new file system
		filesystem = new io::FileSystem;

		//! new driver
		driver = new video::D3DDriver(filesystem);
	}

	SceneManager::~SceneManager()
	{
		if(meshcache)
		{
			meshcache->drop();
		}

		if(filesystem)
		{
			filesystem->drop();
		}

		if(driver)
		{
			driver->drop();
		}
	}

	IAnimateMesh* SceneManager::getmesh(io::IReadFile* rfile)
	{
		IAnimateMesh* mesh = meshcache->getmesh(rfile->getfilename());

		if(!mesh)
		{
			for(u32 i = 0; i < meshloaders.size(); ++i)
			{
				if(meshloaders[i]->isLoaderableFile(rfile->getfilename()))
				{
					mesh = meshloaders[i]->createmesh(rfile);
					break;
				}
			}


			if(mesh)
			{
				meshcache->addmesh(rfile->getfilename(),mesh);
				mesh->drop();
			}
		}

		return mesh;
	}

	IAnimateMesh* SceneManager::getmesh(c8* filename)
	{
		IAnimateMesh* mesh = meshcache->getmesh(filename);

		io::IReadFile* rfile = filesystem->createreadfile(filename);

		if(!rfile) return 0;

		if(!mesh)
		{
			for(u32 i = 0; i < meshloaders.size(); ++i)
			{
				if(meshloaders[i]->isLoaderableFile(filename))
				{
					mesh = meshloaders[i]->createmesh(rfile);
					break;
				}
			}

			if(mesh)
			{
				meshcache->addmesh(filename,mesh);
			}
		}

		return mesh;
	}

	void SceneManager::render()
	{

	}

	ISceneNode* SceneManager::getrootnode()
	{
		return this;
	}

	void SceneManager::registernodeforrendering(ISceneNode* node,E_RENDER_TYPE type = E_RT_DEFAULT)
	{
		if(node == 0) return;

		if(isCulled(node) && (type != E_RT_CAMERA || type != E_RT_LIGHT)) return;

		switch(type)
		{
		case E_RT_CAMERA:
			{
				for(u32 i = 0; i < cameraList.size(); ++i)
				{
					if(cameraList[i] == node) goto _exit;
				}

				cameraList.push_back(node);
				break;
			}
		case E_RT_LIGHT:
			{
				for(u32 i = 0; i < lightList.size(); ++i)
				{
					if(lightList[i] == node) goto _exit;
				}
				lightList.push_back(node);
				break;
			}
		case E_RT_SOLID:
			{
				for(u32 i = 0; i < solidList.size(); ++i)
				{
					if(solidList[i] == node) goto _exit;
				}

				solidList.push_back(node);
				break;
			}
		case E_RT_TERRAIN:
			{
				for(u32 i = 0; i < terrainList.size(); ++i)
				{
					if(terrainList[i] == node) goto _exit;
				}

				terrainList.push_back(node);
				break;
			}

		case E_RT_SKY:
			{
				for(u32 i = 0; i < skyList.size(); ++i)
				{
					if(skyList[i] == node) goto _exit;
				}

				skyList.push_back(node);
				break;
			}

		case E_RT_DEFAULT:
		default:
			{
				for(u32 i = 0; i < defaultList.size(); ++i)
				{
					if(defaultList[i] == node) goto _exit;
				}

				defaultList.push_back(node);
				break;
			}
		}
_exit:;
	}

	void SceneManager::drawall()
	{
		if(ActiveCamera)
		{
			ActiveCamera->onRegisterSceneNode();
			static f32 time = 270;
			f32 x = core::_sin_(time);
			f32 z = core::_cos_(time);
			ActiveCamera->setPosition(core::vector3df(x*300,350,z*300));
		}

		onRegisterSceneNode();

		onAnimate(os::Timer::getTime());

		// render camera
		{
			for(u32 i = 0; i < cameraList.size(); ++i)
			{
				cameraList[i]->render();
			}
		}

		// render light
		{
			for(u32 i = 0; i < lightList.size(); ++i)
			{
				lightList[i]->render();
			}
		}

		// render sky
		{
			for(u32 i = 0; i < skyList.size(); ++i)
			{
				skyList[i]->render();
			}
		}

		// render terrain
		{
			for(u32 i = 0; i < terrainList.size(); ++i)
			{
				terrainList[i]->render();
			}
		}

		// render solid
		{
			for(u32 i = 0; i < solidList.size(); ++i)
			{
				solidList[i]->render();
			}
		}

		// render default
		{
			for(u32 i = 0; i < defaultList.size(); ++i)
			{
				defaultList[i]->render();
			}
		}

	}

	bool SceneManager::render(IAnimateMesh* mesh)
	{
		static u32 frame = 0;
		frame++;
		
		mesh->animate(frame%55);

		u32 buffer_count = mesh->getmeshbuffercount();

		for(u32 i = 0; i < buffer_count; ++i)
		{
			IMeshBuffer* ibuffer = mesh->getmeshbuffer(i);

			MeshBuffer* buffer = static_cast<MeshBuffer*>(ibuffer);

			core::array<u16> index_buffer;
			for(u32 k = 0; k < buffer->indices_buffer.size(); ++k)
			{
				index_buffer.push_back((u16)(buffer->indices_buffer[k]));
			}

			driver->setmaterial(buffer->m_material);

			driver->drawtriangles(buffer->vertex_buffer.pointer(),video::STANDAND_VERTEX,buffer->vertex_buffer.size(),index_buffer.pointer(),buffer->indices_buffer.size()/3);
		}

		return true;
	}

	io::IFileSystem* SceneManager::getfs()
	{
		return filesystem;
	}

	video::IDriver* SceneManager::getdriver()
	{
		return driver;
	}

	ICameraSceneNode* SceneManager::getActiveCamera()
	{
		return ActiveCamera;
	}

	ICameraSceneNode* SceneManager::addCameraSceneNode(ISceneNode* parent,core::vector3df pos, 
		core::vector3df target,core::vector3df up,s32 id)
	{
		if(!parent)
		{
			parent = this;
		}
		if(id == -1) id = ++curr_id;

		ICameraSceneNode* node = new CameraSceneNode(this,parent,id,pos,target,up);
		node->drop();

		setActiveCamera(node);

		return ActiveCamera;
	}

	IMeshSceneNode* SceneManager::addMeshSceneNode(ISceneNode* parent,IMesh* mesh,core::vector3df pos,
		core::vector3df rotate,core::vector3df scale,s32 id /* = -1 */)
	{
		if(!parent)
		{
			parent = this;
		}
		if(id == -1) id = ++curr_id;

		IMeshSceneNode* node = new MeshSceneNode(this,parent,mesh,id,pos,rotate,scale);
		node->drop();

		return node;
	}

	IAnimateMeshSceneNode* SceneManager::addAnimateMeshSceneNode(ISceneNode* parent,IAnimateMesh*mesh, 
		core::vector3df pos,core::vector3df rotate,core::vector3df scale,s32 id /* = -1 */)
	{
		if(!parent)
		{
			parent = this;
		}

		if(id == -1) id = ++curr_id;

		IAnimateMeshSceneNode* node = new AnimateMeshSceneNode(this,parent,mesh,id,pos,rotate,scale);
		node->drop();

		return node;
	}

	IBillboardSceneNode* SceneManager::addBillboardSceneNode(ISceneNode* parent,core::dimention2df size,core::vector3df pos, 
		core::vector3df rotate,core::vector3df scale,s32 id /* = -1 */)
	{
		if(!parent)
		{
			parent = this;
		}

		if(id == -1) id = ++curr_id;

		IBillboardSceneNode* node = new BillboardSceneNode(this,parent,id,size,pos,rotate,scale);
		node->drop();

		return node;
	}

	IParticleSystemSceneNode* SceneManager::addParticleSystemSceneNode(ISceneNode* parent,core::vector3df pos,
		core::vector3df rotate,core::vector3df scale,s32 id /* = -1 */)
	{
		if(!parent)
		{
			parent = this;
		}

		if(id == -1) id = ++curr_id;

		IParticleSystemSceneNode* node = new ParticleSystemSceneNode(this,parent,id,pos,rotate,scale,true);
		node->drop();

		return node;
	}

	ITerrainSceneNode* SceneManager::addTerrainSceneNode(ISceneNode* parent,io::IReadFile* heightMap,video::color32 vColor,
		core::vector3df pos,core::vector3df rotate, core::vector3df scale,u32 MLod,s32 smoothFactor,s32 id /* = -1 */)
	{
		if(!parent)
		{
			parent = this;
		}

		if(id == -1) id = ++curr_id;

		ITerrainSceneNode* node = new TerrainSceneNode(this,parent,id,MLod,E_PS_17,smoothFactor,pos,rotate,scale);
		node->drop();

		if(!node->loadHeightMap(heightMap,vColor,smoothFactor))
		{
			node->removeFromParent();
			node->drop();

			return 0;
		}

		return node;
	}

	ISkyBoxSceneNode* SceneManager::addSkyBoxSceneNode(ISceneNode* parent,video::ITexture* front,video::ITexture* back,
		video::ITexture* left,video::ITexture* right,video::ITexture* top,video::ITexture* bottom,s32 id /* = -1 */)
	{
		if(!parent)
		{
			parent = this;
		}

		if(id == -1) id = ++curr_id;

		ISkyBoxSceneNode* node = new SkyBoxSceneNode(this,parent,id,front,back,left,right,top,bottom,core::vector3df(0,0,0),core::vector3df(0,0,0),core::vector3df(1,1,1));
		node->drop();

		return node;
	}

	void SceneManager::setActiveCamera(ICameraSceneNode* camera)
	{
		if(ActiveCamera)
		{
			ActiveCamera->drop();
		}
		if(camera)
		{
			camera->grab();
			ActiveCamera = camera;
		}
	}

	bool SceneManager::isCulled(ISceneNode* node)
	{
		return false;
		if(cull_type == E_CT_BOX)
		{
			core::aabbox3df bbox = node->getBoundBox();

			const core::aabbox3df frustum_box = getActiveCamera()->getFrustum().getBoundBox();

			return !(frustum_box.isintersectwithbox(bbox));
		}

		if(cull_type == E_CT_PLANE)
		{
			core::aabbox3df bbox = node->getBoundBox();

			bool b = false;

			for(u32 i = 0; i < getActiveCamera()->getFrustum().PLANE_COUNT; ++i)
			{
				if(bbox.getrelationwithplane(getActiveCamera()->getFrustum().Planes[i]) == core::PLANE_FRONT)
				{
					b = true;
					break;
				}
			}

			return b;
		}

		return false;
	}

} // end namespace scene
} // end namespace ISNW